Popular Searches
Popular Course Categories
Popular Courses

Flutter GridView

Flutter Lists & Collections

Flutter GridView

GridView is a Flutter widget used to display widgets in a two-dimensional, scrollable grid. Instead of displaying items one below another like ListView, GridView arranges items into rows and columns.

GridView is commonly used for product catalogs, photo galleries, dashboards, category menus, course cards, shopping applications, and other interfaces where multiple items need to be displayed in a grid.

Flutter provides several GridView constructors, including GridView, GridView.builder, GridView.count, GridView.extent, and GridView.custom. For large or dynamically generated collections, GridView.builder is particularly useful because children are created on demand. Flutter GridView API Documentation

1. What Is GridView?

A GridView displays widgets in a two-dimensional arrangement and provides scrolling when the content is larger than the available space.

A simple grid can look conceptually like this:

+------------+------------+
|   Item 1   |   Item 2   |
+------------+------------+
|   Item 3   |   Item 4   |
+------------+------------+
|   Item 5   |   Item 6   |
+------------+------------+

Each item is called a grid tile or grid child.

2. Where GridView Is Used

  • Product catalogs
  • Photo galleries
  • Food menus
  • Course cards
  • Category screens
  • Shopping applications
  • Social media media grids
  • Dashboard cards
  • App shortcut menus
  • Image galleries
  • Game item collections
  • Portfolio layouts

3. GridView vs ListView

FeatureGridViewListView
LayoutRows and columnsLinear arrangement
Typical useProducts, images, categoriesMessages, users, settings
ScrollingUsually verticalUsually vertical
Multiple columnsYesNo
Dynamic builderGridView.builderListView.builder

4. Basic GridView

The basic GridView constructor accepts an explicit list of widgets and requires a gridDelegate.

GridView(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  children: const [
    Card(child: Center(child: Text('Item 1'))),
    Card(child: Center(child: Text('Item 2'))),
    Card(child: Center(child: Text('Item 3'))),
    Card(child: Center(child: Text('Item 4'))),
  ],
)

The gridDelegate controls the layout, size, spacing, and positioning of grid children. GridView gridDelegate Documentation

5. GridView.count

GridView.count is useful when you know the number of columns or rows you want in the cross axis.

GridView.count(
  crossAxisCount: 2,
  children: const [
    Card(child: Center(child: Text('Item 1'))),
    Card(child: Center(child: Text('Item 2'))),
    Card(child: Center(child: Text('Item 3'))),
    Card(child: Center(child: Text('Item 4'))),
  ],
)

The official Flutter grid recipe demonstrates GridView.count for creating a grid with a fixed number of columns. Flutter Create a Grid List

6. Understanding crossAxisCount

crossAxisCount specifies how many tiles should appear across the cross axis.

GridView.count(
  crossAxisCount: 2,
  children: [
    ...
  ],
)

For a vertical grid:

  • crossAxisCount: 2 → 2 columns
  • crossAxisCount: 3 → 3 columns
  • crossAxisCount: 4 → 4 columns

7. Complete GridView.count Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('GridView Example'),
        ),
        body: GridView.count(
          padding: const EdgeInsets.all(12),
          crossAxisCount: 2,
          mainAxisSpacing: 12,
          crossAxisSpacing: 12,
          children: const [
            Card(
              child: Center(
                child: Text('Laptop'),
              ),
            ),
            Card(
              child: Center(
                child: Text('Mobile'),
              ),
            ),
            Card(
              child: Center(
                child: Text('Tablet'),
              ),
            ),
            Card(
              child: Center(
                child: Text('Headphones'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

8. GridView.builder

GridView.builder is designed for dynamically generated and potentially large collections. It creates grid children on demand instead of requiring all child widgets to be explicitly created up front.

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(products[index]),
      ),
    );
  },
)

Flutter's API documentation describes GridView.builder as a constructor for a scrollable two-dimensional array whose children can be created on demand. It is appropriate for large or potentially infinite collections. GridView.builder API Documentation

9. Why Use GridView.builder?

  • It supports dynamically generated data.
  • It is suitable for large collections.
  • It creates children on demand.
  • It works well with API data.
  • It reduces the need to create a complete widget list before rendering.
  • It supports product, image, category, and dashboard grids.

10. Basic GridView.builder Syntax

GridView.builder(
  itemCount: items.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return YourWidget(
      data: items[index],
    );
  },
)

11. Important GridView.builder Properties

PropertyPurpose
itemCountSpecifies the number of grid items.
itemBuilderBuilds an individual grid item.
gridDelegateControls the grid layout.
crossAxisCountControls the number of columns or rows in the cross axis.
crossAxisSpacingAdds spacing between columns or cross-axis tiles.
mainAxisSpacingAdds spacing between rows or main-axis tiles.
childAspectRatioControls the width-to-height ratio of grid children.
mainAxisExtentSpecifies the extent of children in the main axis when using the fixed-count delegate.
paddingAdds space around the grid.
scrollDirectionControls the scrolling direction.
reverseReverses the scroll direction.
controllerControls and observes scrolling.
physicsControls scrolling behavior.
shrinkWrapAllows the grid to size itself according to its contents when required.

12. SliverGridDelegateWithFixedCrossAxisCount

SliverGridDelegateWithFixedCrossAxisCount is commonly used when you want a fixed number of columns.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
)

The delegate controls the layout of grid tiles, including the number of tiles in the cross axis and their spacing and aspect ratio. SliverGridDelegateWithFixedCrossAxisCount API

13. mainAxisSpacing

mainAxisSpacing controls the spacing between tiles along the main scrolling axis.

GridView.builder(
  itemCount: 20,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text('Item $index'),
      ),
    );
  },
)

14. crossAxisSpacing

crossAxisSpacing controls the space between columns in a vertically scrolling grid.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  crossAxisSpacing: 12,
)

15. Using Both Spacing Properties

GridView.builder(
  itemCount: 20,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text('Item $index'),
      ),
    );
  },
)

16. childAspectRatio

childAspectRatio determines the width-to-height ratio of each grid child.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  childAspectRatio: 1,
)

A value of 1 creates approximately square tiles.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  childAspectRatio: 1.5,
)

A larger ratio produces relatively wider tiles, while a smaller ratio produces relatively taller tiles.

17. mainAxisExtent

mainAxisExtent can be used when you want to specify the main-axis extent of each tile directly.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  mainAxisExtent: 180,
)

This can be useful when cards need a predictable height.

18. GridView.extent

GridView.extent allows you to specify a maximum cross-axis extent for tiles rather than directly specifying the number of columns.

GridView.extent(
  maxCrossAxisExtent: 200,
  padding: const EdgeInsets.all(12),
  children: const [
    Card(child: Center(child: Text('Item 1'))),
    Card(child: Center(child: Text('Item 2'))),
    Card(child: Center(child: Text('Item 3'))),
    Card(child: Center(child: Text('Item 4'))),
  ],
)

This approach can be useful when you want the number of columns to adapt according to the available width. Flutter's GridView documentation identifies GridView.extent as a constructor that creates tiles with a maximum cross-axis extent. GridView Class Documentation

19. SliverGridDelegateWithMaxCrossAxisExtent

The equivalent delegate-based approach uses SliverGridDelegateWithMaxCrossAxisExtent.

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 220,
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

This is useful when the tile width should stay below a maximum value while the number of columns adapts to available space. SliverGridDelegateWithMaxCrossAxisExtent API

20. Dynamic Product Grid

class Product {
  final String name;
  final double price;

  Product({
    required this.name,
    required this.price,
  });
}

final products = [
  Product(name: 'Laptop', price: 55000),
  Product(name: 'Mobile', price: 30000),
  Product(name: 'Tablet', price: 22000),
  Product(name: 'Headphones', price: 5000),
  Product(name: 'Keyboard', price: 2500),
  Product(name: 'Mouse', price: 1200),
];

GridView.builder(
  padding: const EdgeInsets.all(12),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 12,
    mainAxisSpacing: 12,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.shopping_bag,
              size: 50,
            ),
            const SizedBox(height: 10),
            Text(
              product.name,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 6),
            Text('₹${product.price}'),
          ],
        ),
      ),
    );
  },
)

21. Creating a Category Grid

final categories = [
  'Electronics',
  'Fashion',
  'Shoes',
  'Books',
  'Furniture',
  'Beauty',
  'Sports',
  'Grocery',
];

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: categories.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 12,
    mainAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(
          categories[index],
          textAlign: TextAlign.center,
        ),
      ),
    );
  },
)

22. GridView with Icons

final menuItems = [
  {'title': 'Home', 'icon': Icons.home},
  {'title': 'Profile', 'icon': Icons.person},
  {'title': 'Orders', 'icon': Icons.shopping_bag},
  {'title': 'Settings', 'icon': Icons.settings},
];

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: menuItems.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 12,
    mainAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    final item = menuItems[index];

    return Card(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            item['icon'] as IconData,
            size: 40,
          ),
          const SizedBox(height: 8),
          Text(item['title'] as String),
        ],
      ),
    );
  },
)

23. GridView with Images

GridView is frequently used for image galleries.

final imageUrls = [
  'https://example.com/image1.jpg',
  'https://example.com/image2.jpg',
  'https://example.com/image3.jpg',
  'https://example.com/image4.jpg',
];

GridView.builder(
  padding: const EdgeInsets.all(8),
  itemCount: imageUrls.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemBuilder: (context, index) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(12),
      child: Image.network(
        imageUrls[index],
        fit: BoxFit.cover,
      ),
    );
  },
)

24. GridView with Network Images and Loading

GridView.builder(
  itemCount: imageUrls.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemBuilder: (context, index) {
    return Image.network(
      imageUrls[index],
      fit: BoxFit.cover,
      loadingBuilder: (
        context,
        child,
        loadingProgress,
      ) {
        if (loadingProgress == null) {
          return child;
        }

        return const Center(
          child: CircularProgressIndicator(),
        );
      },
    );
  },
)

25. GridView with Card Design

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 14,
    mainAxisSpacing: 14,
    childAspectRatio: 0.75,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return Card(
      elevation: 3,
      clipBehavior: Clip.antiAlias,
      child: Column(
        children: [
          Expanded(
            child: Container(
              width: double.infinity,
              color: Colors.grey.shade200,
              child: const Icon(
                Icons.image,
                size: 60,
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(10),
            child: Column(
              children: [
                Text(
                  product.name,
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 5),
                Text('₹${product.price}'),
              ],
            ),
          ),
        ],
      ),
    );
  },
)

26. GridView with onTap

Grid items can respond to user interaction using InkWell or GestureDetector.

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return InkWell(
      onTap: () {
        print('Selected: ${product.name}');
      },
      child: Card(
        child: Center(
          child: Text(product.name),
        ),
      ),
    );
  },
)

27. Navigating from Grid Item to Detail Screen

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) {
              return ProductDetailsScreen(
                product: product,
              );
            },
          ),
        );
      },
      child: Card(
        child: Center(
          child: Text(product.name),
        ),
      ),
    );
  },
)

28. Responsive GridView

A responsive grid adapts the number of columns according to the available screen width.

LayoutBuilder(
  builder: (context, constraints) {
    int columns;

    if (constraints.maxWidth >= 1200) {
      columns = 5;
    } else if (constraints.maxWidth >= 800) {
      columns = 4;
    } else if (constraints.maxWidth >= 600) {
      columns = 3;
    } else {
      columns = 2;
    }

    return GridView.builder(
      itemCount: products.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: columns,
        crossAxisSpacing: 12,
        mainAxisSpacing: 12,
        childAspectRatio: 0.8,
      ),
      itemBuilder: (context, index) {
        return ProductCard(
          product: products[index],
        );
      },
    );
  },
)

29. Responsive Grid with MaxCrossAxisExtent

Another approach is to allow Flutter to determine the number of columns based on a maximum tile width.

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 250,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

30. GridView Inside a Column

When a GridView is placed inside a Column, it usually needs a bounded height. Expanded is commonly used when the grid should occupy the remaining space.

Column(
  children: [
    const Padding(
      padding: EdgeInsets.all(16),
      child: Text(
        'Products',
        style: TextStyle(fontSize: 24),
      ),
    ),
    Expanded(
      child: GridView.builder(
        itemCount: products.length,
        gridDelegate:
            const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
        ),
        itemBuilder: (context, index) {
          return ProductCard(
            product: products[index],
          );
        },
      ),
    ),
  ],
)

31. Using shrinkWrap

shrinkWrap: true allows a grid to size itself based on its contents along the scroll direction. It can be useful when the grid is embedded inside another layout, but it may require additional layout work.

GridView.builder(
  shrinkWrap: true,
  physics: const NeverScrollableScrollPhysics(),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

For a large screen containing a single primary grid, avoid using shrinkWrap unnecessarily.

32. Horizontal GridView

GridView can also scroll horizontally. In a horizontal grid, the cross axis becomes vertical, so crossAxisCount represents the number of rows.

GridView.builder(
  scrollDirection: Axis.horizontal,
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(products[index].name),
      ),
    );
  },
)

33. GridView with ScrollController

A ScrollController can be used to monitor and control the grid's scrolling position.

final ScrollController controller = ScrollController();

GridView.builder(
  controller: controller,
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

Always dispose of a controller created by a stateful widget when it is no longer needed.

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

34. GridView Reverse Scrolling

GridView.builder(
  reverse: true,
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

The reverse property reverses the scroll direction.

35. GridView Physics

The physics property controls how scrolling behaves.

GridView.builder(
  physics: const BouncingScrollPhysics(),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

To disable scrolling when the grid is nested inside another scrollable:

physics: const NeverScrollableScrollPhysics(),

36. Dynamic Grid with API Data

A common real-world pattern is to fetch products from an API and display them using GridView.builder.

class Product {
  final int id;
  final String name;
  final double price;

  Product({
    required this.id,
    required this.name,
    required this.price,
  });

  factory Product.fromJson(Map json) {
    return Product(
      id: json['id'],
      name: json['name'],
      price: (json['price'] as num).toDouble(),
    );
  }
}

After the API response is converted into a list:

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 12,
    mainAxisSpacing: 12,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return Card(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(product.name),
          const SizedBox(height: 8),
          Text('₹${product.price}'),
        ],
      ),
    );
  },
)

37. Loading, Error, Empty, and Success States

A production grid should handle different data states.

if (isLoading) {
  return const Center(
    child: CircularProgressIndicator(),
  );
}

if (hasError) {
  return const Center(
    child: Text('Failed to load products'),
  );
}

if (products.isEmpty) {
  return const Center(
    child: Text('No products found'),
  );
}

return GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
);

38. Adding Items Dynamically

A GridView can automatically display new items after the underlying list changes and the state is rebuilt.

final List items = [
  'Item 1',
  'Item 2',
  'Item 3',
];

void addItem() {
  setState(() {
    items.add(
      'Item ${items.length + 1}',
    );
  });
}

Display the updated collection:

GridView.builder(
  itemCount: items.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(items[index]),
      ),
    );
  },
)

39. Removing Items Dynamically

void removeItem(int index) {
  setState(() {
    items.removeAt(index);
  });
}

Use the method from a grid item:

GridView.builder(
  itemCount: items.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Stack(
        children: [
          Center(
            child: Text(items[index]),
          ),
          Positioned(
            top: 4,
            right: 4,
            child: IconButton(
              onPressed: () {
                removeItem(index);
              },
              icon: const Icon(Icons.delete),
            ),
          ),
        ],
      ),
    );
  },
)

40. Using Keys in Dynamic Grids

Stable keys can help Flutter preserve widget identity when items are inserted, removed, or reordered.

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return ProductCard(
      key: ValueKey(product.id),
      product: product,
    );
  },
)

If the order of children can change, findChildIndexCallback can also be relevant for preserving state mapping. GridView.builder API Documentation

41. GridView with Selection

class SelectionGrid extends StatefulWidget {
  const SelectionGrid({super.key});

  @override
  State createState() => _SelectionGridState();
}

class _SelectionGridState extends State {
  final List items = [
    'Flutter',
    'Dart',
    'Firebase',
    'Android',
  ];

  int? selectedIndex;

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: items.length,
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 12,
        mainAxisSpacing: 12,
      ),
      itemBuilder: (context, index) {
        final selected = selectedIndex == index;

        return GestureDetector(
          onTap: () {
            setState(() {
              selectedIndex = index;
            });
          },
          child: Card(
            child: Center(
              child: Text(
                items[index],
                style: TextStyle(
                  fontWeight: selected
                      ? FontWeight.bold
                      : FontWeight.normal,
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

42. GridView with Checkbox

final List selectedItems = [
  false,
  false,
  false,
  false,
];

GridView.builder(
  itemCount: items.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: CheckboxListTile(
        title: Text(items[index]),
        value: selectedItems[index],
        onChanged: (value) {
          setState(() {
            selectedItems[index] = value ?? false;
          });
        },
      ),
    );
  },
)

43. GridView and FutureBuilder

For asynchronous data, FutureBuilder can be combined with GridView.builder.

Future> fetchProducts() async {
  await Future.delayed(
    const Duration(seconds: 2),
  );

  return [
    'Laptop',
    'Phone',
    'Tablet',
    'Keyboard',
    'Mouse',
    'Headphones',
  ];
}

FutureBuilder>(
  future: fetchProducts(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    if (snapshot.hasError) {
      return const Center(
        child: Text('Something went wrong'),
      );
    }

    final items = snapshot.data ?? [];

    if (items.isEmpty) {
      return const Center(
        child: Text('No items available'),
      );
    }

    return GridView.builder(
      itemCount: items.length,
      gridDelegate:
          const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 12,
        mainAxisSpacing: 12,
      ),
      itemBuilder: (context, index) {
        return Card(
          child: Center(
            child: Text(items[index]),
          ),
        );
      },
    );
  },
)

44. Pagination with GridView

When a server contains thousands of products, loading everything at once may be unnecessary. Pagination loads data in smaller pages.

  1. Load the first page.
  2. Display the first set of products.
  3. Monitor the scroll position.
  4. When the user approaches the bottom, request another page.
  5. Append the new products to the existing collection.
  6. Continue until the server reports that there are no more items.
final ScrollController controller = ScrollController();

@override
void initState() {
  super.initState();

  controller.addListener(() {
    if (controller.position.pixels >=
        controller.position.maxScrollExtent - 300) {
      loadMoreProducts();
    }
  });
}

Future loadMoreProducts() async {
  // Request the next page from the API.
}

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

45. Avoid API Calls Directly Inside build()

Avoid starting a new network request directly inside build(), because the build method can execute multiple times.

Instead, load data in an appropriate lifecycle method or through your chosen state-management/data-fetching architecture.

@override
void initState() {
  super.initState();
  loadProducts();
}

Future loadProducts() async {
  // Fetch products.
}

46. GridView Performance

GridView.builder uses a builder delegate so grid children can be created on demand. This makes it suitable for large collections. GridView.builder Documentation

Performance Best Practices

  • Use GridView.builder for large or dynamic grids.
  • Provide itemCount whenever the item count is known.
  • Keep grid item widgets reasonably simple.
  • Avoid unnecessary rebuilds.
  • Use stable keys for stateful dynamic items when identity matters.
  • Avoid unnecessary shrinkWrap: true.
  • Avoid unnecessary nested scrollable widgets.
  • Use appropriate image sizes and caching strategies for image-heavy grids.
  • Use pagination for very large remote datasets.
  • Use an appropriate gridDelegate for the required layout.

47. GridView with Reusable ProductCard

Reusable components make large applications easier to maintain.

class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 3,
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.shopping_bag,
              size: 50,
            ),
            const SizedBox(height: 10),
            Text(
              product.name,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 6),
            Text('₹${product.price}'),
          ],
        ),
      ),
    );
  }
}

Then use the reusable card inside the grid:

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 12,
    mainAxisSpacing: 12,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

48. Complete Responsive Product Grid Example

import 'package:flutter/material.dart';

class Product {
  final int id;
  final String name;
  final double price;

  Product({
    required this.id,
    required this.name,
    required this.price,
  });
}

class ProductGridScreen extends StatelessWidget {
  ProductGridScreen({super.key});

  final List products = [
    Product(id: 1, name: 'Laptop', price: 55000),
    Product(id: 2, name: 'Mobile', price: 30000),
    Product(id: 3, name: 'Tablet', price: 22000),
    Product(id: 4, name: 'Keyboard', price: 2500),
    Product(id: 5, name: 'Mouse', price: 1200),
    Product(id: 6, name: 'Headphones', price: 5000),
    Product(id: 7, name: 'Monitor', price: 18000),
    Product(id: 8, name: 'Smart Watch', price: 8000),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: products.length,
        gridDelegate:
            const SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 250,
          mainAxisSpacing: 16,
          crossAxisSpacing: 16,
          childAspectRatio: 0.8,
        ),
        itemBuilder: (context, index) {
          final product = products[index];

          return Card(
            elevation: 3,
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(
                    Icons.shopping_bag,
                    size: 55,
                  ),
                  const SizedBox(height: 12),
                  Text(
                    product.name,
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '₹${product.price}',
                    style: const TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ProductGridScreen(),
    ),
  );
}

49. GridView Constructors

ConstructorPurpose
GridView()Creates a grid with an explicitly supplied list of children and a custom grid delegate.
GridView.builder()Creates grid children on demand and is suitable for large or dynamic collections.
GridView.count()Creates a grid with a fixed number of tiles in the cross axis.
GridView.extent()Creates a grid where tiles have a maximum cross-axis extent.
GridView.custom()Provides custom grid and child delegates.

Flutter's current API documents these GridView constructors and their purposes. GridView Class API

50. GridView vs GridView.builder

GridViewGridView.builder
Uses an explicit list of widgets.Creates widgets through a builder.
Convenient for small collections.Suitable for large or dynamic collections.
All supplied child widgets are part of the explicit child list.Children are created on demand.
Simple for fixed content.Useful for API and database data.

51. GridView vs GridView.count vs GridView.extent

WidgetMain Idea
GridViewCustom grid delegate with explicit children.
GridView.countSpecify the number of cross-axis tiles.
GridView.extentSpecify maximum cross-axis tile extent.
GridView.builderBuild dynamic children on demand.

52. GridView and CustomScrollView

A GridView is built on top of Flutter's sliver-based scrolling system. When an application needs a more complex scrolling layout, a GridView can be combined conceptually with other slivers through CustomScrollView.

For example, a complex page may contain:

  • A SliverAppBar
  • A SliverList
  • A SliverGrid
  • Additional slivers
CustomScrollView(
  slivers: [
    const SliverAppBar(
      title: Text('Products'),
      floating: true,
    ),
    SliverGrid.builder(
      gridDelegate:
          const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: products.length,
      itemBuilder: (context, index) {
        return ProductCard(
          product: products[index],
        );
      },
    ),
  ],
)

Flutter documents GridView as essentially a CustomScrollView containing a single SliverGrid. This makes it possible to migrate to CustomScrollView when a page requires multiple sliver-based sections. GridView and CustomScrollView Documentation

53. Common GridView Mistakes

Mistake 1: Forgetting gridDelegate

GridView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) {
    return Text(products[index].name);
  },
)

GridView.builder requires a gridDelegate.

Mistake 2: Using the Wrong crossAxisCount

If cards become too narrow, reduce the number of columns or use SliverGridDelegateWithMaxCrossAxisExtent.

Mistake 3: Incorrect itemCount

GridView.builder(
  itemCount: 20,
  itemBuilder: ...
)

If the data collection contains fewer than 20 items, indexing it directly may produce an error. When displaying the complete collection, use the actual collection length.

Mistake 4: Unnecessary shrinkWrap

Using shrinkWrap: true everywhere can add unnecessary layout work. Use it when the surrounding layout actually requires it.

Mistake 5: Large Grid with Explicit Children

For large dynamically generated data, prefer GridView.builder instead of constructing a huge explicit widget list.

54. Best Practices

  • Use GridView.builder for large and dynamic grids.
  • Provide itemCount whenever the number of items is known.
  • Use SliverGridDelegateWithFixedCrossAxisCount when you want a specific number of columns.
  • Use SliverGridDelegateWithMaxCrossAxisExtent when you want adaptive tile sizing.
  • Use crossAxisSpacing and mainAxisSpacing for consistent spacing.
  • Use childAspectRatio or mainAxisExtent to control card dimensions.
  • Use reusable widgets such as ProductCard for maintainable code.
  • Use stable keys when dynamic item identity matters.
  • Use pagination for very large API collections.
  • Handle loading, error, and empty states.
  • Avoid unnecessary nested scroll views.
  • Use Expanded when a grid needs to fill available space inside a Column.
  • Use shrinkWrap carefully.
  • Optimize images in image-heavy grids.

55. Practice Exercises

  1. Create a 2-column product grid.
  2. Create a 3-column category grid.
  3. Create a photo gallery using GridView.builder.
  4. Create a responsive product grid using SliverGridDelegateWithMaxCrossAxisExtent.
  5. Add product names and prices to every grid card.
  6. Add an Add Product button that dynamically inserts products.
  7. Add a Delete button to every product card.
  8. Implement product selection using GestureDetector or InkWell.
  9. Navigate from a grid card to a product details page.
  10. Create a grid that loads data from an API.
  11. Add loading, error, and empty states.
  12. Implement pagination for a large product catalog.
  13. Create a horizontal GridView containing two rows.
  14. Build a dashboard using GridView cards.

56. Quick Revision

ConceptKey Point
GridViewDisplays widgets in a scrollable two-dimensional grid.
GridView.builderBuilds grid children on demand.
GridView.countCreates a grid with a fixed cross-axis count.
GridView.extentCreates a grid using maximum cross-axis extent.
gridDelegateControls grid tile layout.
crossAxisCountControls the number of columns in a vertical grid.
mainAxisSpacingControls spacing along the main axis.
crossAxisSpacingControls spacing along the cross axis.
childAspectRatioControls width-to-height ratio.
mainAxisExtentControls child extent along the main axis.
itemCountSpecifies the number of children.
itemBuilderCreates each grid item.
shrinkWrapSizes the grid to its contents when required.
ScrollControllerControls and monitors grid scrolling.

57. Official Flutter Resources

58. Learn Flutter with JustAcademy

For structured Flutter training, practical learning, and course guidance, visit the following resources:

whatsapp